home *** CD-ROM | disk | FTP | other *** search
Text File | 1994-05-10 | 40.6 KB | 1,404 lines |
- ┌────────┬───────────────────────────────────────────────────────────────────
- │ GUSDOC │
- └────────┘
-
-
-
-
-
- THE OFFICAL
-
-
-
- GRAVIS ULTRASOUND PROGRAMMERS ENCYCLOPEDIA
-
- ( G.U.P.E )
-
-
-
- v 0.1
-
-
- Written by Mark Dixon.
-
-
-
-
-
-
- -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
-
- INTRODUCTION
- ~~~~~~~~~~~~
- The Gravis Ultrasound is by far the best & easiest sound card to
- program. Why? Because the card does all the hard stuff for you, leaving
- you and the CPU to do other things! This reference will document some
- (but not all) of the Gravis Ultrasound's hardware functions, allowing
- you to play music & sound effects on your GUS.
-
- We will not be going into great detail as to the theory behind
- everything - if you want to get technical information then read the
- GUS SDK. We will be merely providing you with the routines necessary
- to play samples on the GUS, and a basic explanation of how they work.
-
- This document will NOT go into DMA transfer or MIDI specifications.
- If someone knows something about them, and would like to write some
- info on them, we would appreciate it very much.
-
- All source code is in Pascal (tested under Turbo Pascal v7.0, but
- should work with TP 6.0 and possibly older versions). This document
- will assume reasonable knowledge of programming, and some knowledge of
- soundcards & music.
-
-
- INITIALISATION & AUTODETECTION
- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- Since we are not using DMA, we only need to find the GUS's I/O port,
- which can be done from the DOS environment space, or preferably from a
- routine that will scan all possible I/O ports until it finds a GUS.
-
- The theory behind the detection routine is to store some values into
- GUS memory, and then read them back. If we have the I/O port correct,
- we will read back exactly what we wrote. So first, we need a routine
- that will write data to the memory of the GUS :
-
-
- Function GUSPeek(Loc : Longint) : Byte;
-
- { Read a value from GUS memory }
-
- Var
- B : Byte;
- AddLo : Word;
- AddHi : Byte;
- Begin
- AddLo := Loc AND $FFFF;
- AddHi := LongInt(Loc AND $FF0000) SHR 16;
-
- Port [Base+$103] := $43;
- Portw[Base+$104] := AddLo;
- Port [Base+$103] := $44;
- Port [Base+$105] := AddHi;
-
- B := Port[Base+$107];
- GUSPeek := B;
- End;
-
-
- Procedure GUSPoke(Loc : Longint; B : Byte);
-
- { Write a value into GUS memory }
-
- Var
- AddLo : Word;
- AddHi : Byte;
- Begin
- AddLo := Loc AND $FFFF;
- AddHi := LongInt(Loc AND $FF0000) SHR 16;
- Port [Base+$103] := $43;
- Portw[Base+$104] := AddLo;
- Port [Base+$103] := $44;
- Port [Base+$105] := AddHi;
- Port [Base+$107] := B;
- End;
-
-
- Since the GUS can have up to 1meg of memory, we need to use a 32bit
- word to address all possible memory locations. However, the hardware of
- the GUS will only accept a 24bit word, which means we have to change
- the 32bit address into a 24bit address. The first two lines of each
- procedure does exactly that.
-
- The rest of the procedures simply send commands and data out through
- the GUS I/O port defined by the variable BASE (A word). So to test for
- the presence of the GUS, we simply write a routine to read/write memory
- for all possible values of BASE :
-
-
- Function GUSProbe : Boolean;
-
- { Returns TRUE if there is a GUS at I/O address BASE }
-
- Var
- B : Byte;
- Begin
- Port [Base+$103] := $4C;
- Port [Base+$105] := 0;
- GUSDelay;
- GUSDelay;
- Port [Base+$103] := $4C;
- Port [Base+$105] := 1;
- GUSPoke(0, $AA);
- GUSPoke($100, $55);
- B := GUSPeek(0);
- If B = $AA then GUSProbe := True else GUSProbe := False;
- End;
-
-
- Procedure GUSFind;
-
- { Search all possible I/O addresses for the GUS }
-
- Var
- I : Word;
- Begin
- for I := 1 to 8 do
- Begin
- Base := $200 + I*$10;
- If GUSProbe then I := 8;
- End;
- If Base < $280 then
- Write('Found your GUS at ', Base, ' ');
- End;
-
-
- The above routines will obviously need to be customised for your own
- use - for example, setting a boolean flag to TRUE if you find a GUS,
- rather than just displaying a message.
-
- It is also a good idea to find out exactly how much RAM is on the
- GUS, and this can be done in a similar process to the above routine.
- Since the memory can either be 256k, 512k, 768k or 1024k, all we have
- to do is to read/write values on the boundaries of these memory
- addresses. If we read the same value as we wrote, then we know exactly
- how much memory is available.
-
-
- Function GUSFindMem : Longint;
-
- { Returns how much RAM is available on the GUS }
-
- Var
- I : Longint;
- B : Byte;
- Begin
- GUSPoke($40000, $AA);
- If GUSPeek($40000) <> $AA then I := $3FFFF
- else
- Begin
- GUSPoke($80000, $AA);
- If GUSPeek($80000) <> $AA then I := $8FFFF
- else
- Begin
- GUSPoke($C0000, $AA);
- If GUSPeek($C0000) <> $AA then I := $CFFFF
- else I := $FFFFF;
- End;
- End;
- GUSFindMem := I;
- End;
-
-
- Now that we know where the GUS is, and how much memory it has, we
- need to initialise it for output. Unfortunately, the below routine is
- slightly buggy. If you run certain programs (I discovered this after
- running Second Reality demo) that use the GUS, and then your program
- using this init routine, it will not initialise the GUS correctly.
-
- It appears that I am not doing everything that is necessary to
- initialise the GUS. However, I managed to correct the problem by
- either re-booting (not a brilliant solution) or running Dual Module
- Player, which seems to initialise it properly. If someone knows where
- i'm going wrong, please say so!
-
- Anyway, the following routine should be called after you have found
- the GUS, and before you start doing anything else with the GUS.
-
-
-
- Procedure GUSDelay; Assembler;
-
- { Pause for approx. 7 cycles. }
-
- ASM
- mov dx, 0300h
- in al, dx
- in al, dx
- in al, dx
- in al, dx
- in al, dx
- in al, dx
- in al, dx
- End;
-
-
- Procedure GUSReset;
-
- { An incomplete routine to initialise the GUS for output. }
-
- Begin
- port [Base+$103] := $4C;
- port [Base+$105] := 1;
- GUSDelay;
- port [Base+$103] := $4C;
- port [Base+$105] := 7;
- port [Base+$103] := $0E;
- port [Base+$105] := (14 OR $0C0);
- End;
-
-
- Now you have all the routine necessary to find and initialise the
- GUS, let's see just what we can get the GUS to do!
-
-
- MAKING SOUNDS
- ~~~~~~~~~~~~~
- The GUS is unique in that it allows you to store the data to be
- played in it's onboard DRAM. To play the sample, you then tell it what
- frequency to play it at, what volume and pan position, and which sample
- to play. The GUS will then do everything in the background, it will
- interpolate the data to give an effective 44khz (or less, depending on
- how many active voices) sample. This means that an 8khz sample will
- sound better on the GUS than most other cards, since the GUS will play
- it at 44khz!
-
- The GUS also has 32 seperate digital channels (that are mixed by a
- processor on the GUS) which all have their own individual samples,
- frequencies, volumes and panning positions. For some reason, however,
- the GUS can only maintain 44khz output with 16 channels - the more
- channels, the lower the playback rate (which basically means, lower
- quality). If you are using all 32 channels (unlikely), then playback is
- reduced to 22khz.
-
- Since you allready know how to store samples in the GUS dram (simply
- use the GUSPoke routine to store the bytes) we will now look at various
- routines to change the way the gus plays a sample. The first routine we
- will look at will set the volume of an individual channel :
-
- Procedure GUSSetVolume( Voi : Byte; Vol : Word);
-
- { Set the volume of channel VOI to Vol, a 16bit logarithmic scale
- volume value - 0 is off, $ffff is full volume, $e0000 is half
- volume, etc }
-
- Begin
- Port [Base+$102] := Voi;
- Port [Base+$102] := Voi;
- Port [Base+$102] := Voi;
- Port [Base+$103] := 9;
- Portw[Base+$104] := Vol; { 0-0ffffh, log scale not linear }
- End;
-
- The volume (and pan position & frequency) can be changed at ANY time
- regardless of weather the GUS is allready playing the sample or not.
- This means that to fade out a sample, you simply make several calls to
- the GUSSetVolume routine with exponentially (to account for the
- logarithmic scale) decreasing values.
-
- The next two routines will set the pan position (from 0 to 15, 0
- being left, 15 right and 7 middle) and the frequency respectively :
-
- Procedure GUSSetBalance( V, B : Byte);
- Begin
- Port [Base+$102] := V;
- Port [Base+$102] := V;
- Port [Base+$102] := V;
- Port [Base+$103] := $C;
- Port [Base+$105] := B;
- End;
-
- Procedure GUSSetFreq( V : Byte; F : Word);
- Begin
- Port [Base+$102] := V;
- Port [Base+$102] := V;
- Port [Base+$102] := V;
- Port [Base+$103] := 1;
- Portw[Base+$104] := F;
- End;
-
- I'm not sure the the value F in the set frequency procedure. The GUS
- SDK claims that it is the exact frequency at which the sample should be
- played.
-
- When playing a sample, it is necessary to set the volume, position
- and frequency BEFORE playing the sample. In order to start playing a
- sample, you need to tell the GUS where abouts in memory the sample is
- stored, and how big the sample is :
-
-
- Procedure GUSPlayVoice( V, Mode : Byte;VBegin, VStart, VEnd : Longint);
-
- { This routine tells the GUS to play a sample commencing at VBegin,
- starting at location VStart, and stopping at VEnd }
-
- Var
- GUS_Register : Word;
- Begin
- Port [Base+$102] := V;
- Port [Base+$102] := V;
- Port [Base+$103] := $0A;
- Portw[Base+$104] := (VBegin SHR 7) AND 8191;
- Port [Base+$103] := $0B;
- Portw[Base+$104] := (VBegin AND $127) SHL 8;
- Port [Base+$103] := $02;
- Portw[Base+$104] := (VStart SHR 7) AND 8191;
- Port [Base+$103] := $03;
- Portw[Base+$104] := (VStart AND $127) SHL 8;
- Port [Base+$103] := $04;
- Portw[Base+$104] := ((VEnd) SHR 7) AND 8191;
- Port [Base+$103] := $05;
- Portw[Base+$104] := ((VEnd) AND $127) SHL 8;
- Port [Base+$103] := $0;
- Port [Base+$105] := Mode;
-
- { The below part isn't mentioned as necessary, but the card won't
- play anything without it! }
-
- Port[Base] := 1;
- Port[Base+$103] := $4C;
- Port[Base+$105] := 3;
- end;
-
- There are a few important things to note about this routine. Firstly,
- the value VEnd refers to the location in memory, not the length of the
- sample. So if the sample commenced at location 1000, and was 5000 bytes
- long, the VEnd would be 6000 if you wanted the sample to play to the
- end. VBegin and VStart are two weird values, one of them defines the
- start of the sample, and the other defines where abouts to actually
- start playing. I'm not sure why both are needed, since I have allways
- set them to the same value.
-
- Now that the gus is buisy playing a sample, the CPU is totally free
- to be doing other things. We might, for example, want to spy on the gus
- and see where it is currently up to in playing the sample :
-
- Function VoicePos( V : Byte) : Longint;
- Var
- P : Longint;
- Temp0, Temp1 : Word;
- Begin
- Port [Base+$102] := V;
- Port [Base+$102] := V;
- Port [Base+$102] := V;
- Port [Base+$103] := $8A;
- Temp0 := Portw[Base+$104];
- Port [Base+$103] := $8B;
- Temp1 := Portw[Base+$104];
- VoicePos := (Temp0 SHL 7)+ (Temp1 SHR 8);
- End;
-
- This routine will return the memory location that the channel V is
- currently playing. If the GUS has reached the end of the sample, then
- the returned value will be VEnd. If you want to see what BYTE value is
- currently being played (for visual output of the sample's waveform),
- then you simply PEEK the location pointed to by this routine.
-
- Finally, we might want to stop playing the sample before it has
- reached it's end - the following routine will halt the playback on
- channel V.
-
-
- Procedure GUSStopVoice( V : Byte);
- Var
- Temp : Byte;
- Begin
- Port [Base+$102] := V;
- Port [Base+$102] := V;
- Port [Base+$102] := V;
- Port [Base+$103] := $80;
- Temp := Port[Base+$105];
- Port [Base+$103] := 0;
- Port [Base+$105] := (Temp AND $df) OR 3;
- GUSDelay;
- Port [Base+$103] := 0;
- Port [Base+$105] := (Temp AND $df) OR 3;
- End;
-
-
- SPECIAL EFFECTS
- ~~~~~~~~~~~~~~~
- There are a few extra features of the GUS that are worthy of mention,
- the main one being hardware controlled sample looping. The GUS has a
- control byte for each of the 32 channels. This control byte consists of
- 8 flags that effect the way the sample is played, as follows :
- ( The table is taken directly from the GUS Software Developers Kit )
-
- =================================
- | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
- =================================
- | | | | | | | |
- | | | | | | | +---- Voice Stopped
- | | | | | | +-------- Stop Voice
- | | | | | +------------ 16 bit data
- | | | | +---------------- Loop enable
- | | | +-------------------- Bi-directional loop enable
- | | +------------------------ Wave table IRQ
- | +---------------------------- Direction of movement
- +-------------------------------- IRQ pending
- (*)Bit 0 = 1 : Voice is stopped. This gets set by hitting the end
- address (not looping) or by setting bit 1 in this reg.
- Bit 1 = 1 : Stop Voice. Manually force voice to stop.
- Bit 2 = 1 : 16 bit wave data, 0 = 8 bit data
- Bit 3 = 1 : Loop to begin address when it hits the end address.
- Bit 4 = 1 : Bi-directional looping enabled
- Bit 5 = 1 : Enable wavetable IRQ. Generate an irq when the voice
- hits the end address. Will generate irq even if looping
- is enabled.
- (*)Bit 6 = 1 - Decreasing addresses, 0 = increasing addresses. It is
- self-modifying because it might shift directions when
- it hits one of the loop boundaries and looping is enabled.
- (*)Bit 7 = 1 - Wavetable IRQ pending. If IRQ's are enabled and
- looping is NOT enabled, an IRQ will be constantly
- generated until voice is stopped. This means that
- you may get more than 1 IRQ if it isn't handled
- properly.
-
-
- Procedure GUSVoiceControl( V, B : Byte);
- Begin
- Port [Base+$102] := V;
- Port [Base+$102] := V;
- Port [Base+$103] := $0;
- Port [Base+$105] := B;
- End;
-
-
- The above routine will set the Voice Control byte for the channel
- defined in V. For example, if you want channel 1 to play the sample in
- a continuous loop, you would use the procedure like this :
-
- GUSVoiceControl( 1, $F ); { Bit 3 ON = $F }
-
-
- CONCLUSION
- ~~~~~~~~~~
-
- The above routines are all that is necessary to get the GUS to start
- playing music. To prove this, I have included my 669 player & source
- code in the package as a practical example. The GUSUnit contains all
- the routines discussed above. I won't go into the theory of the 669
- player, but it is a good starting point if you want to learn about
- modplayers. The player is contained within the archive 669UNIT.ARJ
-
-
-
- ┌────────┬───────────────────────────────────────────────────────────────────
- │ README │
- └────────┘
-
-
- GUS669 Unit v0.2b
- Copyright 1994 Mark Dixon.
- (aka C.D. of Silicon Logic)
-
-
- LEGAL STUFF
- ~~~~~~~~~~~
- I'd like to avoid this, but it has to be done. Basically, if anything
- in this archive causes any kind of damage, I cannot be held
- responsable - USE AT YOUR OWN RISK.
-
- In adition, since I spent long hours working on this project, and
- attempting to decode the GUS SDK, I would appreciate it if people
- didn't rip off my work. Give me credit for what I have done, and if
- your planning to use my routines for commercial purposes, talk to me
- first, or you might find yourself on the wrong side of a legal battle.
- (Hey, let's sound tough while i'm at it, I have lawyer's in the
- family, so it's not gonna cost me much to sue someone. And don't
- criticise my spelling! :)
-
-
-
- BORING STUFF
- ~~~~~~~~~~~~
- Well, if your the sort of person who likes to ignore all the rubishy
- bits that go into a README text file, then you'd better stop now and
- go and try out the source code!
-
- Basically, this readme isn't going to say much more than what the
- source code is, and then go dribling on for five pages about
- absolutely nothing.
-
-
- SOURCE CODE! DID SOMEONE SAY - SOURCE CODE!! - ????
- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- Yes, that's right, free with every download of this wonderful archive
- comes the complete Pascal source code to a 669 module player for the
- GUS. I'd have included my MOD player, but I haven't been able to get
- all the MOD commands working, so you'll just have to make do with a
- 669 player :)
-
- Feel free to make use of this source code for any non-commercial
- purposes you might be able to think of - and mention my name while
- your at it! Since the source code is here, people are bound to modify
- it for their personal uses. If you do this, I would very much like to
- see your modifications - so that I can include them in the next
- release of the player.
-
-
- Well, I don't want to bore you anymore, and it's getting late (not!)
- so i'd better let you go and play around with the source code :)
-
-
- SILICON LOGIC
- ~~~~~~~~~~~~~
- What ever happened to Silicon Logic? Well, after being killed off over
- in Perth, a major revival is underway here in Canberra, with a more
- commercial view - more on that later.
-
- For those of you who have never heard of Silicon Logic, then you're
- either not Australian, or not into the ausie demo scene. But then,
- that covers about 99.999999999999% of the world population :)
-
-
- GREETINGS
- ~~~~~~~~~
- I've allways wanted to dribble some thanks, so here goes.
-
- Thanks go to...
-
- Darren Lyon - Who got me into this programming lark in the first
- place. Finally wrote myself a mod player :)
- Tran - Your source code really helped!
- Kitsune - Love those mods, keep up the good work!
-
- ... and Advanced Gravis, for making the best sound card ever.
-
- Greetings to...
-
- FiRE members - I'll probably never join you guys, but good luck
- anyway!
- UNiQUE - How's the board going?
- CRaSH - Still ripping other peoples source code?
- Old SL members - Thanks for the support, good luck with your new
- group!
- Oliver White - G'day... just thought i'd say hi, since you so
- kindly beta tested the player for me.
- Murray Head - Rick Price sux! :-) SoundBlaster sux too! :-)
- Perth people - I'm coming back... someday!
-
-
- THE PICK / MINNOW - Hey, give me a call sometime, long time no
- talk...
-
-
-
- INTERESTED IN A DEMO GROUP IN CANBERRA?
- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- If there is anyone interested in joining a demo / coding group in
- Canberra (ACT), then drop me a line.
-
-
-
- ┌────────────┬───────────────────────────────────────────────────────────────
- │ GUSUNIT.PAS│
- └────────────┘
-
- Unit GUSUnit;
-
- {
- GUS DigiUnit v1.0
- Copyright 1994 Mark Dixon.
-
- This product is "Learnware".
-
- All contents of this archive, including source and executables, are the
- intellectual property of the author, Mark Dixon. Use of this product for
- commercial programs, or commercial gain in ANY way, is illegal. Private
- use, or non-commercial use (such as demos, PD games, etc) is allowed,
- provided you give credit to the author for these routines.
-
- Feel free to make any modifications to these routines, but I would
- appreciate it if you sent me these modifications, so that I can include
- them in the next version of the Gus669 Unit.
-
- If you wish to use these routines for commercial purposes, then you will
- need a special agreement. Please contact me, Mark Dixon, and we can work
- something out.
-
- What's "Learnware"? Well, I think I just made it up actually. What i'm
- getting at is that the source code is provided for LEARNING purposes only.
- I'd get really angry if someone ripped off my work and tried to make out
- that they wrote a mod player.
-
- As of this release (Gus699 Unit), the Gus DigiUnit has moved to version
- 1.0, and left the beta stage. I feel these routines are fairly sound,
- and I haven't made any changes to them in weeks.
-
-
- Notice the complete absence of comments here? Well, that's partially
- the fault of Gravis and their SDK, since it was so hard to follow, I
- was more worried about getting it working than commenting it. No offense
- to Gravis though, since they created this wonderful card! :-) It helps
- a lot if you have the SDK as a reference when you read this code,
- otherwise you might as well not bother reading it.
-
- }
-
-
-
- INTERFACE
-
- Procedure GUSPoke(Loc : Longint; B : Byte);
- Function GUSPeek(Loc : Longint) : Byte;
- Procedure GUSSetFreq( V : Byte; F : Word);
- Procedure GUSSetBalance( V, B : Byte);
- Procedure GUSSetVolume( Voi : Byte; Vol : Word);
- Procedure GUSPlayVoice( V, Mode : Byte;VBegin, VStart, VEnd : Longint);
- Procedure GUSVoiceControl( V, B : Byte);
- Procedure GUSReset;
- Function VoicePos( V : Byte) : Longint;
-
- Const
- Base : Word = $200;
- Mode : Byte = 0;
-
- IMPLEMENTATION
-
-
- Uses Crt;
-
- Function Hex( W : Word) : String;
- Var
- I, J : Word;
- S : String;
- C : Char;
- Const
- H : Array[0..15] of Char = ('0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F');
- Begin
- S := '';
- S := S + H[(W DIV $1000) MOD 16];
- S := S + H[(W DIV $100 ) MOD 16];
- S := S + H[(W DIV $10 ) MOD 16];
- S := S + H[(W DIV $1 ) MOD 16];
- Hex := S+'h';
- End;
-
-
- Procedure GUSDelay; Assembler;
- ASM
- mov dx, 0300h
- in al, dx
- in al, dx
- in al, dx
- in al, dx
- in al, dx
- in al, dx
- in al, dx
- End;
-
-
-
- Function VoicePos( V : Byte) : Longint;
- Var
- P : Longint;
- I, Temp0, Temp1 : Word;
- Begin
- Port [Base+$102] := V;
- Port [Base+$103] := $8A;
- Temp0 := Portw[Base+$104];
- Port [Base+$103] := $8B;
- Temp1 := Portw[Base+$104];
- VoicePos := (Temp0 SHL 7)+ (Temp1 SHR 8);
- For I := 1 to 10 do GusDelay;
- End;
-
-
- Function GUSPeek(Loc : Longint) : Byte;
- Var
- B : Byte;
- AddLo : Word;
- AddHi : Byte;
- Begin
- AddLo := Loc AND $FFFF;
- AddHi := LongInt(Loc AND $FF0000) SHR 16;
-
- Port [Base+$103] := $43;
- Portw[Base+$104] := AddLo;
- Port [Base+$103] := $44;
- Port [Base+$105] := AddHi;
-
- B := Port[Base+$107];
- GUSPeek := B;
- End;
-
-
- Procedure GUSPoke(Loc : Longint; B : Byte);
- Var
- AddLo : Word;
- AddHi : Byte;
- Begin
- AddLo := Loc AND $FFFF;
- AddHi := LongInt(Loc AND $FF0000) SHR 16;
- { Write('POKE HI :', AddHi:5, ' LO : ', AddLo:5, ' ');}
- Port [Base+$103] := $43;
- Portw[Base+$104] := AddLo;
- Port [Base+$103] := $44;
- Port [Base+$105] := AddHi;
- Port [Base+$107] := B;
- { Writeln(B:3);}
- End;
-
-
- Function GUSProbe : Boolean;
- Var
- B : Byte;
- Begin
- Port [Base+$103] := $4C;
- Port [Base+$105] := 0;
- GUSDelay;
- GUSDelay;
- Port [Base+$103] := $4C;
- Port [Base+$105] := 1;
- GUSPoke(0, $AA);
- GUSPoke($100, $55);
- B := GUSPeek(0);
- { Port [Base+$103] := $4C;
- Port [Base+$105] := 0;}
- { Above bit disabled since it appears to prevent the GUS from accessing
- it's memory correctly.. in some bizare way.... }
-
- If B = $AA then GUSProbe := True else GUSProbe := False;
- End;
-
-
- Procedure GUSFind;
- Var
- I : Word;
- Begin
- for I := 1 to 8 do
- Begin
- Base := $200 + I*$10;
- If GUSProbe then I := 8;
- End;
- If Base < $280 then
- Write('Found your GUS at ', Hex(Base), ' ');
- End;
-
-
- Function GUSFindMem : Longint;
- { Returns how much RAM is available on the GUS }
- Var
- I : Longint;
- B : Byte;
- Begin
- GUSPoke($40000, $AA);
- If GUSPeek($40000) <> $AA then I := $3FFFF
- else
- Begin
- GUSPoke($80000, $AA);
- If GUSPeek($80000) <> $AA then I := $8FFFF
- else
- Begin
- GUSPoke($C0000, $AA);
- If GUSPeek($C0000) <> $AA then I := $CFFFF
- else I := $FFFFF;
- End;
- End;
- GUSFindMem := I;
- End;
-
-
- Procedure GUSSetFreq( V : Byte; F : Word);
- Begin
- Port [Base+$102] := V;
- Port [Base+$102] := V;
- Port [Base+$102] := V;
- Port [Base+$103] := 1;
- Portw[Base+$104] := (F { DIV 19}); { actual frequency / 19.0579083837 }
- End;
-
- Procedure GUSVoiceControl( V, B : Byte);
- Begin
- Port [Base+$102] := V;
- Port [Base+$102] := V;
- Port [Base+$103] := $0;
- Port [Base+$105] := B;
- End;
-
-
-
- Procedure GUSSetBalance( V, B : Byte);
- Begin
- Port [Base+$102] := V;
- Port [Base+$102] := V;
- Port [Base+$102] := V;
- Port [Base+$103] := $C;
- Port [Base+$105] := B;
- End;
-
-
- Procedure GUSSetVolume( Voi : Byte; Vol : Word);
- Begin
- Port [Base+$102] := Voi;
- Port [Base+$102] := Voi;
- Port [Base+$102] := Voi;
- Port [Base+$103] := 9;
- Portw[Base+$104] := Vol; { 0-0ffffh, log ... not linear }
- End;
-
-
- Procedure GUSSetLoopMode( V : Byte);
- Var
- Temp : Byte;
- Begin
- Port [Base+$102] := V;
- Port [Base+$102] := V;
- Port [Base+$102] := V;
- Port [Base+$103] := $80;
- Temp := Port[Base+$105];
- Port [Base+$103] := 0;
- Port [Base+$105] := (Temp AND $E7) OR Mode;
- End;
-
-
- Procedure GUSStopVoice( V : Byte);
- Var
- Temp : Byte;
- Begin
- Port [Base+$102] := V;
- Port [Base+$102] := V;
- Port [Base+$102] := V;
- Port [Base+$103] := $80;
- Temp := Port[Base+$105];
- Port [Base+$103] := 0;
- Port [Base+$105] := (Temp AND $df) OR 3;
- GUSDelay;
- Port [Base+$103] := 0;
- Port [Base+$105] := (Temp AND $df) OR 3;
- End;
-
-
- Procedure GUSPlayVoice( V, Mode : Byte;VBegin, VStart, VEnd : Longint);
- Var
- GUS_Register : Word;
- Begin
- Port [Base+$102] := V;
- Port [Base+$102] := V;
- Port [Base+$103] := $0A;
- Portw[Base+$104] := (VBegin SHR 7) AND 8191;
- Port [Base+$103] := $0B;
- Portw[Base+$104] := (VBegin AND $127) SHL 8;
- Port [Base+$103] := $02;
- Portw[Base+$104] := (VStart SHR 7) AND 8191;
- Port [Base+$103] := $03;
- Portw[Base+$104] := (VStart AND $127) SHL 8;
- Port [Base+$103] := $04;
- Portw[Base+$104] := ((VEnd) SHR 7) AND 8191;
- Port [Base+$103] := $05;
- Portw[Base+$104] := ((VEnd) AND $127) SHL 8;
- Port [Base+$103] := $0;
- Port [Base+$105] := Mode;
-
- { The below part isn't mentioned as necessary, but the card won't
- play anything without it! }
-
- Port[Base] := 1;
- Port[Base+$103] := $4C;
- Port[Base+$105] := 3;
-
- end;
-
-
- Procedure GUSReset;
- Begin
- port [Base+$103] := $4C;
- port [Base+$105] := 1;
- GUSDelay;
- port [Base+$103] := $4C;
- port [Base+$105] := 7;
- port [Base+$103] := $0E;
- port [Base+$105] := (14 OR $0C0);
- End;
-
-
-
- Var
- I : Longint;
- F : File;
- Buf : Array[1..20000] of Byte;
- S : Word;
-
-
- Begin
- Clrscr;
- Writeln('GUS DigiUnit V1.0');
- Writeln('Copyright 1994 Mark Dixon.');
- Writeln;
- GUSFind;
- Writeln('with ', GUSFindMem, ' bytes onboard.');
- Writeln;
- GUSReset;
- End.
-
-
- ┌────────────┬───────────────────────────────────────────────────────────────
- │ GUS669.PAS │
- └────────────┘
-
- UNIT Gus669;
-
- {
- GUS669 Unit v0.2b
- Copyright 1994 Mark Dixon.
-
- This product is "Learnware".
-
- All contents of this archive, including source and executables, are the
- intellectual property of the author, Mark Dixon. Use of this product for
- commercial programs, or commercial gain in ANY way, is illegal. Private
- use, or non-commercial use (such as demos, PD games, etc) is allowed,
- provided you give credit to the author for these routines.
-
- Feel free to make any modifications to these routines, but I would
- appreciate it if you sent me these modifications, so that I can include
- them in the next version of the Gus669 Unit.
-
- If you wish to use these routines for commercial purposes, then you will
- need a special agreement. Please contact me, Mark Dixon, and we can work
- something out.
-
- What's "Learnware"? Well, I think I just made it up actually. What i'm
- getting at is that the source code is provided for LEARNING purposes only.
- I'd get really angry if someone ripped off my work and tried to make out
- that they wrote a mod player.
-
- Beta version? Yes, since the product is still slightly unstable, I feel
- it is right to keep it under beta status until I find and fix a few
- bugs.
-
- FEATURES
- - Only works with the GUS!
- - 8 channel, 669 music format.
- - That's about it really.
- - Oh, 100% Pascal high level source code = NO ASSEMBLER!
- (So if you want to learn about how to write your own MOD player, this
- should make it easier for you)
- - Tested & compiled with Turbo Pascal v7.0
-
- BUGS
- - Not yet, give me a chance!
- (If you find any, I would very much appreciate it if you could take
- the time to notify me)
- - Doesn't sound right with some modules, advice anyone??
- - Could do with some better I/O handling routines when loading the
- 669 to give better feedback to the user about what went wrong
- if the module didn't load.
-
-
- You can contact me at any of the following :
-
- FidoNet : Mark Dixon 3:620/243
- ItnerNet : markd@cairo.anu.edu.au ( prefered )
- d9404616@karajan.anu.edu.au ( might not work for mail :) )
- sdixonmj@cc.curtin.edu.au ( Don't use this one often )
- sdixonmj01@cc.curtin.edu.au ( Might not exist any more,
- that's how often it's used! )
- I collect internet accounts.... :)
-
- If you happen to live in the Australian Capital Territory, you can
- call me on 231-2000, but at respectable hours please.
-
-
- "Want more comments? Write em!"
- Sorry, I just had to quote that. I'm not in the mood for writing lots
- of comments just yet. The main reason for writing it in Pascal is so
- that it would be easy to understand. Comments may (or may not) come later
- on.
-
- Okay, enough of me dribbling, here's the source your after!
-
- }
-
-
-
-
- Interface
-
- Procedure Load669(N : String);
- Procedure PlayMusic;
- Procedure StopMusic;
-
- Type
- { This is so that we can keep a record of what each channel is
- currently doing, so that we can inc/dec the Frequency or volume,
- or pan left/right, etc }
- Channel_Type = Record
- Vol : Word;
- Freq : Word;
- Pan : Byte;
- End;
-
- Var
- Channels : Array[1..8] of Channel_Type;
- Flags : Array[0..15] of Byte;
- { Programmer flags. This will be explained when it is fully implemented. }
-
- Const
- Loaded : Boolean = False; { Is a module loaded? }
- Playing : Boolean = False; { Is a module playing? }
- WaitState : Boolean = False; { Set to TRUE whenever a new note is played }
- { Helpful for timing in with the player }
-
-
- Const
- NumChannels = 8;
-
- { Thanks to Tran for releasing the Hell demo source code, from which
- I managed to find these very helpfull volume and frequency value
- tables, without which this player would not have worked! }
-
- voltbl : Array[0..15] of Byte =
- ( $004,$0a0,$0b0,$0c0,$0c8,$0d0,$0d8,$0e0,
- $0e4,$0e8,$0ec,$0f1,$0f4,$0f6,$0fa,$0ff);
- freqtbl : Array[1..60] of Word = (
- 56,59,62,66,70,74,79,83,88,94,99,105,
- 112,118,125,133,141,149,158,167,177,188,199,211,
- 224,237,251,266,282,299,317,335,355,377,399,423,
- 448,475,503,532,564,598,634,671,711,754,798,846,
- 896,950,1006,1065,1129,1197,1268,1343,1423,1508,1597,1692 );
-
-
-
- Type
- Header_669_Type = Record
- Marker : Word;
- Title : Array[1..108] of Char;
- NOS, { No of Samples 0 - 64 }
- NOP : Byte; { No of Patterns 0 - 128 }
- LoopOrder : Byte;
- Order : Array[0..127] of Byte;
- Tempo : Array[0..127] of Byte;
- Break : Array[0..127] of Byte;
- End;
- Sample_Type = Record
- FileName : Array[1..13] of Char;
- Length : Longint;
- LoopStart : Longint;
- LoopLen : Longint;
- End;
- Sample_Pointer = ^Sample_Type;
- Note_Type = Record
- Info, { <- Don't worry about this little bit here }
- Note,
- Sample,
- Volume,
- Command,
- Data : Byte;
- End;
- Event_Type = Array[1..8] of Note_Type;
- Pattern_Type = Array[0..63] of Event_Type;
- Pattern_Pointer = ^Pattern_Type;
-
-
-
- Var
- Header : Header_669_Type;
- Samples : Array[0..64] of Sample_Pointer;
- Patterns : Array[0..128] of Pattern_Pointer;
- GusTable : Array[0..64] of Longint;
- GusPos : Longint;
- Speed : Byte;
- Count : Word;
- OldTimer : Procedure;
- CurrentPat, CurrentEvent : Byte;
-
-
- Implementation
-
- Uses Dos, Crt, GUSUnit;
-
-
- Procedure Load669(N : String);
- Var
- F : File;
- I, J, K : Byte;
- T : Array[1..8,1..3] of Byte;
-
- Procedure LoadSample(No, Size : Longint);
- Var
- Buf : Array[1..1024] of Byte;
- I : Longint;
- J, K : Integer;
- Begin
- GusTable[No] := GusPos;
-
- I := Size;
- While I > 1024 do
- Begin
- BlockRead(F, Buf, SizeOf(Buf), J);
- For K := 1 to J do GusPoke(GusPos+K-1, Buf[K] XOR 127);
- Dec(I, J);
- Inc(GusPos, J);
- End;
- BlockRead(F, Buf, I, J);
- For K := 1 to J do GusPoke(GusPos+K-1, Buf[K] XOR 127);
- Inc(GusPos, J);
- End;
-
- Begin
- {$I-}
- Assign(F, N);
- Reset(F, 1);
- BlockRead(F, Header, SizeOf(Header));
- If Header.Marker = $6669 then
- Begin
- For I := 1 to Header.NOS do
- Begin
- New(Samples[I-1]);
- BlockRead(F, Samples[I-1]^, SizeOf(Samples[I-1]^));
- End;
-
- For I := 0 to Header.NOP-1 do
- Begin
- New(Patterns[I]);
- For J := 0 to 63 do
- Begin
- BlockRead(F, T, SizeOf(T));
- For K := 1 to 8 do
- Begin
- Patterns[I]^[J,K].Info := t[K,1];
- Patterns[I]^[J,K].Note := ( t[K,1] shr 2);
- Patterns[I]^[J,K].Sample := ((t[K,1] AND 3) SHL 4) + (t[K,2] SHR 4);
- Patterns[I]^[J,K].Volume := ( t[K,2] AND 15);
- Patterns[I]^[J,K].Command := ( t[K,3] shr 4);
- Patterns[I]^[J,K].Data := ( t[K,3] AND 15);
- End;
- End;
- End;
-
- For I := 1 to Header.NOS do
- LoadSample(I-1, Samples[I-1]^.Length);
- End;
-
- Close(F);
- {$I+}
- If (IOResult <> 0) OR (Header.Marker <> $6669) then
- Loaded := False else Loaded := True;
-
- End;
-
-
-
-
- Procedure UpDateNotes;
- Var
- I : Word;
- Inst : Byte;
- Note : Word;
- Begin
- WaitState := True;
- For I := 1 to NumChannels do
- With Patterns[Header.Order[CurrentPat]]^[CurrentEvent, I] do
-
- For I := 1 to NumChannels do
- If (Patterns[Header.Order[CurrentPat]]^[CurrentEvent, I].Info < $FE) then
- Begin
- Inst := Patterns[Header.Order[CurrentPat]]^[CurrentEvent, I].Sample;
- Note := Patterns[Header.Order[CurrentPat]]^[CurrentEvent, I].Note;
- Channels[I].Freq := FreqTbl[Note];
- { Channels[I].Pan := (1-(I AND 1)) * 15;}
- Channels[I].Vol := $100*VolTbl[Patterns[Header.Order[CurrentPat]]^[CurrentEvent, I].Volume];
- { Write(Note:3,Inst:3,' -');}
-
- GUSSetVolume (I, 0);
- GUSVoiceControl (I, 1);
- GUSSetBalance (I, Channels[I].Pan);
- GusSetFreq ( I, Channels[I].Freq);
- { GUSPlayVoice ( I, 0, GusTable[Inst],
- GusTable[Inst],
- GusTable[Inst]+Samples[Inst]^.Length );}
-
- { Write(Samples[Inst]^.LoopLen:5);}
- If Samples[Inst]^.LoopLen < 1048575 then
- Begin
- GUSPlayVoice ( I, 8, GusTable[Inst],
- GusTable[Inst]+Samples[Inst]^.LoopStart,
- GusTable[Inst]+Samples[Inst]^.LoopLen );
- End
- Else
- Begin
- GUSPlayVoice ( I, 0, GusTable[Inst],
- GusTable[Inst],
- GusTable[Inst]+Samples[Inst]^.Length );
- End;
-
-
- End;
-
- { Writeln;}
-
- For I := 1 to NumChannels do
- If (Patterns[Header.Order[CurrentPat]]^[CurrentEvent, I].Info < $FF) then
- GUSSetVolume (I, $100*VolTbl[Patterns[Header.Order[CurrentPat]]^[CurrentEvent, I].Volume]);
-
- For I := 1 to NumChannels do
- With Patterns[Header.Order[CurrentPat]]^[CurrentEvent, I] do
- Case Command of
- 5 : Speed := Data;
- 3 : Begin
- Channels[I].Freq := Channels[I].Freq + 10;
- GUSSetFreq(I, Channels[I].Freq);
- End;
- 8 : Inc(Flags[Data]);
- 6 : Case Data of
- 0 : If Channels[I].Pan > 0 then
- Begin
- Dec(Channels[I].Pan);
- GusSetBalance(I, Channels[I].Pan);
- End;
- 1 : If Channels[I].Pan < 15 then
- Begin
- Inc(Channels[I].Pan);
- GusSetBalance(I, Channels[I].Pan);
- End;
- End;
- End;
-
-
-
-
-
- Inc(CurrentEvent);
- If CurrentEvent > Header.Break[CurrentPat] then Begin CurrentEvent := 0; Inc(CurrentPat) End;
- If Header.Order[CurrentPat] > (Header.NOP) then Begin CurrentEvent := 0; CurrentPat := 0; End;
-
- End;
-
-
- Procedure UpDateEffects;
- Var
- I : Word;
- Begin
- For I := 1 to 4 do
- With Patterns[Header.Order[CurrentPat]]^[CurrentEvent, I] do
- Begin
- Case Command of
- 0 : Begin
- Inc(Channels[I].Freq, Data);
- GusSetFreq(I, Channels[I].Freq);
- End;
- 1 : Begin
- Dec(Channels[I].Freq, Data);
- GusSetFreq(I, Channels[I].Freq);
- End;
- End;
- End;
- End;
-
-
-
-
- { $ F+,S-,W-}
- Procedure ModInterrupt; Interrupt;
- Begin
- Inc(Count);
- If Count = Speed then
- Begin
- UpDateNotes;
- Count := 0;
- End;
- UpDateEffects;
- If (Count MOD 27) = 1 then
- Begin
- inline ($9C);
- OldTimer;
- End;
- Port[$20] := $20;
- End;
- { $ F-,S+}
-
- Procedure TimerSpeedup(Speed : Word);
- Begin
- Port[$43] := $36;
- Port[$40] := Lo(Speed);
- Port[$40] := Hi(Speed);
- end;
-
- Procedure PlayMusic;
- Begin
- If Loaded then
- Begin
- TimerSpeedUp( (1192755 DIV 32));
- GetIntVec($8, Addr(OldTimer));
- SetIntVec($8, Addr(ModInterrupt));
- Speed := Header.Tempo[0];
- Playing := True;
- End
- { If the module is not loaded, then the Playing flag will not be set,
- so your program should check the playing flag just after calling
- PlayMusic to see if everything was okay. }
- End;
-
-
- Procedure StopMusic;
- Var
- I : Byte;
- Begin
- If Playing then
- Begin
- SetIntVec($8, Addr(OldTimer));
- For I := 1 to NumChannels do GusSetVolume(I, 0);
- End;
- TimerSpeedUp($FFFF);
- End;
-
-
- Procedure Init;
- Var
- I : Byte;
- Begin
- GusPos := 1;
- Count := 0;
- Speed := 6;
- CurrentPat := 0;
- CurrentEvent := 0;
- For I := 1 to NumChannels do Channels[I].Pan := (1-(I AND 1)) * 15;
- For I := 1 to NumChannels do GUSVoiceControl(I, 1);
- For I := 0 to 15 do Flags[I] := 0;
- End;
-
-
- Var
- I, J : Byte;
-
-
- Begin
- Init;
- Writeln('GUS669 Unit V0.2b');
- Writeln('Copyright 1994 Mark Dixon.');
- Writeln;
- End.
-
-
- ┌─────────────┬──────────────────────────────────────────────────────────────
- │ PLAY669.PAS │
- └─────────────┘
-
- Program Testout_Gus669_Unit;
-
- Uses Crt, GUS669;
-
- Begin
-
- If ParamCount > 0 then Load669(Paramstr(1))
- else
- Begin
- Writeln;
- Writeln('Please specify the name of the 669 module you wish to play');
- Writeln('from the command line.');
- Writeln;
- Writeln('eg : Play669 Hardwired.669 ');
- Writeln;
- Halt(1);
- End;
- PlayMusic;
- If Playing then
- Begin
- Writeln('Playing ', ParamStr(1) );
- Writeln('Press any key to stop and return to DOS.');
- Repeat
- Until Keypressed
- End
- else
- Begin
- Writeln;
- Writeln('Couldn''t load or play the module for some reason!');
- Writeln;
- Writeln('Please check your GUS is working correctly, and that you have');
- Writeln('correctly specified the 669 filename.');
- Writeln;
- End;
- StopMusic;
- End.
-